package com.example.redis.service;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSON;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class JedisLockUtil {

	private static StringRedisTemplate redisTemplate;

	@Resource
	public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
		this.redisTemplate = stringRedisTemplate;
	}

	private static ThreadLocal<String> label = new ThreadLocal<>();

	/**
	 * 删除分布式锁lua脚本
	 */
	private final static String unlockScript = "if tostring(redis.call('get', KEYS[1])) == tostring(ARGV[1]) then return redis.call('del', KEYS[1]) else return 0 end";

	/**
	 * 生成全局唯一token校验lua脚本
	 */
	private final static String tokenScript = "if tonumber(redis.call('exists', KEYS[1])) == 0 then  redis.call('set', KEYS[1],ARGV[1],'ex',ARGV[2]) return 1 else return 0 end";

	public static void clear() {
		label.remove();
	}

	/**
	 * 获取redis锁
	 * ----慎用------
	 * ----慎用------
	 * ----慎用------
	 * ThreadLock无法解决加多把分布式锁的问题
	 *
	 * @param lockKey
	 * @param i
	 * @return
	 */
	public static boolean tryLock(String lockKey, int i, int timeout) {
		if (i <= 0) {
			throw new IllegalArgumentException("参数i不能小于0");
		}
		label.set(System.currentTimeMillis() + "" + new Random().nextInt(10000));
		boolean flag = false;
		for (int j = 0; j < i; j++) {
			flag = (boolean) redisTemplate.execute((RedisCallback) connection -> {
				RedisAsyncCommands command = (RedisAsyncCommands) connection.getNativeConnection();
				SetArgs args = new SetArgs(); // 相当于设置一个规范，可应用到其他键值对
				args.ex(timeout); // 过期时间100s
				args.nx(); // 创建键值对之前先判断是否存在，存在则不创建
				String result = command.getStatefulConnection()
						.sync().set(lockKey.getBytes(), JSON.toJSONString(label.get()).getBytes(), args);
//                    String result = jedis.set(lockKey, JSON.toJSONString(label.get()), "nx", "ex", timeout);
				return "OK".equals(result);
			});
			if (flag) {
				return true;
			} else {
				try {
					Thread.sleep(j % 10 + new Random().nextInt(20));
				} catch (InterruptedException e) {
					log.error("获取redis分布式锁异常", e);
				}
			}
		}
		return flag;

	}

	/**
	 * 释放redis锁
	 * ----慎用------
	 * ----慎用------
	 * ----慎用------
	 * ThreadLock无法解决加多把分布式锁的问题
	 *
	 * @param lockKey
	 */
	public void deleteLock(String lockKey) {
		String realValue = "-1";
		try {
			realValue = (String) redisTemplate.opsForValue().get(lockKey);
			realValue = (realValue == null ? "-1" : realValue);
		} catch (Exception e) {
			log.error("释放redis锁出现异常", e);
			redisTemplate.delete(lockKey);
		} finally {
			String s = label.get();
			if (!("-1".equals(realValue)) && s != null && s.equals(realValue)) {
				redisTemplate.delete(lockKey);
			}

		}
	}

	public void deleteLockForce(String lockKey) {
		redisTemplate.delete(lockKey);
	}

	/**
	 * 加锁
	 *
	 * @param lockKey           加锁的key
	 * @param attempts          加锁失败,总共尝试次数
	 * @param expireTime        到期时间 单位:秒
	 * @param unlockCertificate 解锁凭证 每个线程必须对应不同的凭证
	 * @return
	 */
	public static boolean tryLockFriendly(String lockKey, int attempts, int expireTime, String unlockCertificate) {
		if (attempts <= 0 || expireTime <= 0) {
			throw new IllegalArgumentException("重试次数或锁超时时间必须大于0");
		}
		if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(unlockCertificate)) {
			throw new IllegalArgumentException("加锁的key或解锁凭证不能为空");
		}
		boolean result = false;
		for (int j = 1; j <= attempts; j++) {
			result = redisTemplate.opsForValue().setIfAbsent(lockKey, unlockCertificate, expireTime,
					TimeUnit.SECONDS);
			if ((!result) && (j != attempts)) {
				try {
					Thread.sleep(j % 10 + new Random().nextInt(20));
				} catch (InterruptedException e) {
					log.error("获取redis分布式锁异常", e);
				}
			} else {
				return result;
			}
		}
		return result;
	}

	/**
	 * 解锁
	 *
	 * @param lockKey           加锁的key
	 * @param unlockCertificate 解锁凭证 每个线程必须对应不同的凭证
	 */
	public static Long unLockFriendly(String lockKey, String unlockCertificate) {
		Long result = 0l;
		if (StringUtils.isBlank(lockKey) || StringUtils.isBlank(unlockCertificate)) {
			throw new IllegalArgumentException("加锁的key或解锁凭证不能为空");
		}
		try {
			result = redisTemplate.execute((RedisCallback<Long>) connection ->
				//注意这里的ReturnType要是分实际返回类型不匹配的话，会报NullException
				connection.eval(unlockScript.getBytes(),
						ReturnType.INTEGER,
						1, lockKey.getBytes(), unlockCertificate.getBytes()));
			return result;
		} catch (Exception e) {
			log.error("删除分布式锁异常，参数lockkey:{},unlockCertificate:{}", lockKey, unlockCertificate);
			log.error("删除分布式锁异常", e);
		}
		return result;
	}

	/***
	 * 生成n位随机字母和数字组合
	 */
	private String randomMix(int length) {
		if (length <= 0) {
			length = 10;
		} else if (length > 50) {
			length = 50;
		}
		String val = "";
		Random random = new Random();
		for (int i = 0; i < length; i++) {
			String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
			if ("char".equalsIgnoreCase(charOrNum)) {
				int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
				val += (char) (random.nextInt(26) + temp);
			} else if ("num".equalsIgnoreCase(charOrNum)) {
				val += String.valueOf(random.nextInt(10));
			}
		}
		return val;
	}

	/**
	 * @return 解锁凭证
	 */
	public static String getCertificate(String module) {
		StringBuilder sb = new StringBuilder(module);
		sb.append(System.currentTimeMillis());
		sb.append(RandomUtil.randomNumbers(6));
		return sb.toString();
	}

	public static void main(String[] args) {
		JedisLockUtil util = new JedisLockUtil();
		String str = util.randomMix(3);
		System.out.println(getCertificate("we"));
	}

}
